package dbPhase.hypeerweb;


/*Used to send a message from a source node to a target node. The actual message is the targetOperation to be performed on the target Node.
 * The "targetOperation" is abstract and is to be overridden in a visitor that does the actual work.
 * There is also an intermediateOperation that may be performed on nodes visited on the way to the target node.

 Domain:
     None*/
public abstract class SendVisitor implements Visitor {

	public static String TARGET_KEY = "TARGET";

	/*The default constructor.
Pre-condition:
None
Post-condition:
True*/
	public SendVisitor() {
		//TODO
	}

/*The SendVisitor visitor expects the parameters to contain a target. This method initializes the parameters with the key-pair (TARGET_KEY, target).
 * If more parameters are required in a subclass this method is overridden. Normally this method is only called by the source node before sending the "message".
Parameters:
target - the webId of the node we are to perform the target operation on.
Pre-condition:
∃ n (n ∈ HyPeerWeb AND n.webId = target)
Post-condition:
(TARGET_KEY, target) ∈ result AND |result| = 1*/
	public static Parameters createInitialParameters(int target) {
		Parameters p = new Parameters();
		p.set(TARGET_KEY, target);
		return p;
	}

	/*The intermediate operation to be performed on a node as we traverse to the target node. Often this method does nothing.
Parameters:
node - the intermediate node.
parameters - the parameters to be used when executing the intermediate operation.
Pre-condition:
node ≠ null AND parameters ≠ null;
Post-condition:
TRUE*/
	protected void intermediateOperation(Node node, Parameters parameters){}

	/*The abstract operation to be performed on the targetNode. Must be overridden in any concrete subclass.
Parameters:
node - the target node this operation is to be performed on.
parameters - the list of parameters to be passed to the target operation.
Pre-condition:
node ≠ null AND parameters ≠ null
Post-condition:
TRUE*/
	protected abstract void targetOperation(Node node, Parameters parameters);

	/*If the webId of the node = the target in the parameters then the targetOperation method is performed on the node.
	 * Otherwise the itermediateOperation mtheod is performed on the node (this method often does nothing),
	 * a node closer to the target node is found, and the accept method is executed on that node passing in this SendVisitor and the parameters.
Specified by:
visit in interface Visitor
Parameters:
node - the node being visited in the visitor pattern.
parameters - the parameters being passed along to the node then to the SendVisitor.
Pre-condition:
node ≠ null AND parameters ≠ null AND
∃ target (target ∈ HyPeerWeb AND (TARGET_KEY, node.webId) ∈ parameters)
Post-condition:
node.webId = parameters.get(TARGET_KEY) ⇒ targetOperation(node, parameters).post-condition
ELSE
        itermediateOperation(node, parameters).post-condition AND
        &exist closerNode (
                closerNode ∈ node.connections AND
                distanceBetween(closerNode.webId, parameters.get(TARGET_KEY)) < distanceBetween(node.webId, parameters.get(TARGET_KEY)) AND
                NOT ∃ otherNode(
                        otherNode ∈ node.connections AND
                        distance(otherNode.webId, parameters.get(TARGET_KEY) < distance(closerNode.webId, parameters.get(TARGET_KEY)) AND
                closerNode.accept(this, parameters).post-condition)*/
	public void visit(Node node, Parameters parameter) {
		int target = (Integer)parameter.get(TARGET_KEY);
		WebId targetId = new WebId(target);
		if (node.webId.distanceTo(targetId) == 0) {
			targetOperation(node, parameter);
		}
		else {
			intermediateOperation(node, parameter);
			Node closerNode = Null_Node.getSingleton();
			int closerDistance = (node.webId.getHeight() + 2);
			Node NULLNODE = Null_Node.getSingleton();
			if (node.fold != NULLNODE && node.fold.webId.distanceTo(targetId) < closerDistance) {
				closerNode = node.fold;
				closerDistance = closerNode.webId.distanceTo(targetId);
			}
			if (node.surrogateFold != NULLNODE && node.surrogateFold.webId.distanceTo(targetId) < closerDistance) {
				closerNode = node.surrogateFold;
				closerDistance = closerNode.webId.distanceTo(targetId);
			}
			if (node.inverseSurrogateFold != NULLNODE && node.inverseSurrogateFold.webId.distanceTo(targetId) < closerDistance) {
				closerNode = node.inverseSurrogateFold;
				closerDistance = closerNode.webId.distanceTo(targetId);
			}

			for (Node n : node.neighbors) {
				if (n.webId.distanceTo(targetId) < closerDistance) {
					closerNode = n;
					closerDistance = closerNode.webId.distanceTo(targetId);
				}
			}

			for (Node n : node.upPointers) {
				if (n.webId.distanceTo(targetId) < closerDistance) {
					closerNode = n;
					closerDistance = closerNode.webId.distanceTo(targetId);
				}
			}

			for (Node n : node.downPointers) {
				if (n.webId.distanceTo(targetId) < closerDistance) {
					closerNode = n;
					closerDistance = closerNode.webId.distanceTo(targetId);
				}
			}

			if (closerNode.webId.distanceTo(targetId) <= node.webId.distanceTo(targetId)) {
				closerNode.accept(this, parameter);
			}
			else {
				System.err.println("WebId " + target + " does not exist");
			}
		}
	}
}
